﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SmartMathLibrary.InternalRoutines;

namespace SmartMathLibrary.Statistics.RandomNumberGenerator
{
    /// <summary>
    /// Generates a single random deviate from an exponential distribution with mean AV.
    /// </summary>
    [Serializable]
    public class ExponentialRandomDeviateGenerator : AbstractRandomGenerator
    {
        /// <summary>
        /// The mean of the exponential distribution from which a random deviate is to be generated.
        /// </summary>
        private readonly double av;

        /// <summary>
        /// Internal linker to the base routine.
        /// </summary>
        private readonly StatisticalRandomRoutines provider;

        /// <summary>
        /// Initializes a new instance of the <see cref="ExponentialRandomDeviateGenerator"/> class.
        /// </summary>
        /// <param name="av">The mean of the exponential distribution from which a random deviate is to be generated.</param>
        public ExponentialRandomDeviateGenerator(double av)
            : base((uint) DateTime.Now.Millisecond)
        {
            if (av == 0)
            {
                throw new ArgumentException("The parameter av has to be different from 0.");
            }

            this.av = av;
            this.provider = new StatisticalRandomRoutines((int) this.Seed);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ExponentialRandomDeviateGenerator"/> class.
        /// </summary>
        /// <param name="av">The mean of the exponential distribution from which a random deviate is to be generated.</param>
        /// <param name="seed">A number used to calculate a starting value for the pseudo-random number sequence.
        /// If a negative number is specified, the absolute value of the number is used.</param>
        public ExponentialRandomDeviateGenerator(double av, uint seed)
            : base(seed)
        {
            if (av == 0)
            {
                throw new ArgumentException("The parameter av has to be different from 0.");
            }

            this.av = av;
            this.provider = new StatisticalRandomRoutines((int) seed);
        }

        /// <summary>
        /// Returns a nonnegative random number.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer greater than or equal to zero and less than maximum integer value.
        /// </returns>
        public override int Next()
        {
            return this.Next(0, int.MaxValue);
        }

        /// <summary>
        /// Nexts the specified max value.
        /// </summary>
        /// <param name="maxValue">Returns a nonnegative random number less than the specified maximum.</param>
        /// <returns>
        /// A 32-bit signed integer greater than or equal to zero and less than maximum integer value.
        /// </returns>
        public override int Next(int maxValue)
        {
            return this.Next(0, maxValue);
        }

        /// <summary>
        /// Returns a random number within a specified range.
        /// </summary>
        /// <param name="minValue">The inclusive lower bound of the random number returned.</param>
        /// <param name="maxValue">The exclusive upper bound of the random number returned. maxValue must be
        /// greater than or equal to minValue.</param>
        /// <returns>
        /// A 32-bit signed integer greater than or equal to zero and less than maximum integer value.
        /// </returns>
        public override int Next(int minValue, int maxValue)
        {
            if (minValue < 0)
            {
                minValue = 0;
            }

            if (maxValue == int.MaxValue)
            {
                maxValue -= 1;
            }

            if (minValue > maxValue)
            {
                int tempuri = maxValue;

                maxValue = minValue;
                minValue = tempuri;
            }

            double number = this.GenerateRandomNumber();

            if (number < 0)
            {
                number = Math.Abs(number);
            }

            if (number > 1)
            {
                number = number - Math.Truncate(number);
            }

            return (int) (Math.Floor((maxValue - minValue + 1)*number + minValue));
        }

        /// <summary>
        /// Returns a random number between 0.0 and 1.0.
        /// </summary>
        /// <returns>
        /// A double-precision floating point number greater than or equal to 0.0, and less
        /// than 1.0.
        /// </returns>
        public override double NextDouble()
        {
            return this.GenerateRandomNumber();
        }

        private double GenerateRandomNumber()
        {
            return this.provider.genexp(this.av);
        }
    }
}